/*
 * Copyright (C) 2014 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.util.concurrent;

import static com.google.common.util.concurrent.MoreExecutors.directExecutor;

import com.google.caliper.BeforeExperiment;
import com.google.caliper.Param;
import com.google.caliper.api.Footprint;
import com.google.caliper.api.SkipThisScenarioException;
import com.google.common.util.concurrent.AbstractFutureBenchmarks.Facade;
import com.google.common.util.concurrent.AbstractFutureBenchmarks.Impl;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;

/**
 * Measures the size of AbstractFuture implementations.
 */
public class AbstractFutureFootprintBenchmark {

    enum State {
        NOT_DONE, FINISHED, CANCELLED, FAILED
    }

    @Param
    State state;
    @Param
    Impl impl;
    @Param({"0", "1", "5", "10"})
    int numListeners;
    @Param({"0", "1", "5", "10"})
    int numThreads;

    private final Set<Thread> blockedThreads = new HashSet<Thread>();

    @BeforeExperiment
    void setUp() throws Exception {
        if (state != State.NOT_DONE && (numListeners != 0 || numThreads != 0)) {
            throw new SkipThisScenarioException();
        }
    }

    // This exclusion doesn't exclude the TOMBSTONE objects we set. So 'done' NEW futures will look
    // larger than they are.
    @Footprint(exclude = {Runnable.class, Executor.class, Thread.class, Exception.class})
    public Object measureSize() {
        for (Thread thread : blockedThreads) {
            thread.interrupt();
        }
        blockedThreads.clear();
        final Facade<Object> f = impl.newFacade();
        for (int i = 0; i < numThreads; i++) {
            Thread thread = new Thread() {
                @Override
                public void run() {
                    try {
                        f.get();
                    } catch (Throwable expected) {
                    }
                }
            };
            thread.start();
            blockedThreads.add(thread);
        }
        for (int i = 0; i < numListeners; i++) {
            f.addListener(Runnables.doNothing(), directExecutor());
        }
        for (Thread thread : blockedThreads) {
            AbstractFutureBenchmarks.awaitWaiting(thread);
        }
        switch (state) {
            case NOT_DONE:
                break;
            case FINISHED:
                f.set(null);
                break;
            case CANCELLED:
                f.cancel(false);
                break;
            case FAILED:
                f.setException(new Exception());
                break;
            default:
                throw new AssertionError();
        }
        return f;
    }
}
